#pragma once

#include "../defines.hpp"
#include <deque>
#include "WLClasses.hpp"
#include <vector>
#include <array>
#include <memory>
#include <xf86drmMode.h>
#include "Timer.hpp"
#include "math/Math.hpp"
#include <optional>
#include "signal/Signal.hpp"
#include "DamageRing.hpp"
#include <aquamarine/output/Output.hpp>
#include <aquamarine/allocator/Swapchain.hpp>

// Enum for the different types of auto directions, e.g. auto-left, auto-up.
enum eAutoDirs {
    DIR_AUTO_NONE = 0, /* None will be treated as right. */
    DIR_AUTO_UP,
    DIR_AUTO_DOWN,
    DIR_AUTO_LEFT,
    DIR_AUTO_RIGHT
};

struct SMonitorRule {
    eAutoDirs           autoDir     = DIR_AUTO_NONE;
    std::string         name        = "";
    Vector2D            resolution  = Vector2D(1280, 720);
    Vector2D            offset      = Vector2D(0, 0);
    float               scale       = 1;
    float               refreshRate = 60;
    bool                disabled    = false;
    wl_output_transform transform   = WL_OUTPUT_TRANSFORM_NORMAL;
    std::string         mirrorOf    = "";
    bool                enable10bit = false;
    drmModeModeInfo     drmMode     = {};
    std::optional<int>  vrr;
};

class CMonitor;
class CSyncTimeline;

class CMonitorState {
  public:
    CMonitorState(CMonitor* owner);
    ~CMonitorState();

    bool commit();
    bool test();
    bool updateSwapchain();

  private:
    void      ensureBufferPresent();

    CMonitor* m_pOwner;
};

class CMonitor {
  public:
    CMonitor();
    ~CMonitor();

    Vector2D                    vecPosition         = Vector2D(-1, -1); // means unset
    Vector2D                    vecXWaylandPosition = Vector2D(-1, -1); // means unset
    Vector2D                    vecSize             = Vector2D(0, 0);
    Vector2D                    vecPixelSize        = Vector2D(0, 0);
    Vector2D                    vecTransformedSize  = Vector2D(0, 0);

    bool                        primary = false;

    uint64_t                    ID                     = -1;
    PHLWORKSPACE                activeWorkspace        = nullptr;
    PHLWORKSPACE                activeSpecialWorkspace = nullptr;
    float                       setScale               = 1; // scale set by cfg
    float                       scale                  = 1; // real scale

    std::string                 szName             = "";
    std::string                 szDescription      = "";
    std::string                 szShortDescription = "";

    Vector2D                    vecReservedTopLeft     = Vector2D(0, 0);
    Vector2D                    vecReservedBottomRight = Vector2D(0, 0);

    drmModeModeInfo             customDrmMode = {};

    CMonitorState               state;
    CDamageRing                 damage;

    SP<Aquamarine::IOutput>     output;
    float                       refreshRate     = 60;
    int                         framesToSkip    = 0;
    int                         forceFullFrames = 0;
    bool                        noFrameSchedule = false;
    bool                        scheduledRecalc = false;
    wl_output_transform         transform       = WL_OUTPUT_TRANSFORM_NORMAL;
    float                       xwaylandScale   = 1.f;
    std::array<float, 9>        projMatrix      = {0};
    std::optional<Vector2D>     forceSize;
    SP<Aquamarine::SOutputMode> currentMode;
    SP<Aquamarine::CSwapchain>  cursorSwapchain;

    bool                        dpmsStatus       = true;
    bool                        vrrActive        = false; // this can be TRUE even if VRR is not active in the case that this display does not support it.
    bool                        enabled10bit     = false; // as above, this can be TRUE even if 10 bit failed.
    bool                        createdByUser    = false;
    bool                        isUnsafeFallback = false;

    bool                        pendingFrame    = false; // if we schedule a frame during rendering, reschedule it after
    bool                        renderingActive = false;

    wl_event_source*            renderTimer  = nullptr; // for RAT
    bool                        RATScheduled = false;
    CTimer                      lastPresentationTimer;

    bool                        isBeingLeased = false;

    SMonitorRule                activeMonitorRule;

    // explicit sync
    SP<CSyncTimeline> inTimeline;
    SP<CSyncTimeline> outTimeline;
    uint64_t          lastWaitPoint = 0;
    uint64_t          commitSeq     = 0;

    WP<CMonitor>      self;

    // mirroring
    CMonitor*              pMirrorOf = nullptr;
    std::vector<CMonitor*> mirrors;

    // for tearing
    PHLWINDOWREF solitaryClient;

    // for direct scanout
    PHLWINDOWREF lastScanout;

    struct {
        bool canTear         = false;
        bool nextRenderTorn  = false;
        bool activelyTearing = false;

        bool busy                    = false;
        bool frameScheduledWhileBusy = false;
    } tearingState;

    struct {
        CSignal destroy;
        CSignal connect;
        CSignal disconnect;
        CSignal dpmsChanged;
        CSignal modeChanged;
    } events;

    std::array<std::vector<PHLLSREF>, 4> m_aLayerSurfaceLayers;

    // methods
    void     onConnect(bool noRule);
    void     onDisconnect(bool destroy = false);
    void     addDamage(const pixman_region32_t* rg);
    void     addDamage(const CRegion* rg);
    void     addDamage(const CBox* box);
    bool     shouldSkipScheduleFrameOnMouseEvent();
    void     setMirror(const std::string&);
    bool     isMirror();
    bool     matchesStaticSelector(const std::string& selector) const;
    float    getDefaultScale();
    void     changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal = false, bool noMouseMove = false, bool noFocus = false);
    void     changeWorkspace(const int& id, bool internal = false, bool noMouseMove = false, bool noFocus = false);
    void     setSpecialWorkspace(const PHLWORKSPACE& pWorkspace);
    void     setSpecialWorkspace(const int& id);
    void     moveTo(const Vector2D& pos);
    Vector2D middle();
    void     updateMatrix();
    int64_t  activeWorkspaceID();
    int64_t  activeSpecialWorkspaceID();
    CBox     logicalBox();
    void     scheduleDone();
    bool     attemptDirectScanout();

    bool     m_bEnabled             = false;
    bool     m_bRenderingInitPassed = false;

    // For the list lookup

    bool operator==(const CMonitor& rhs) {
        return vecPosition == rhs.vecPosition && vecSize == rhs.vecSize && szName == rhs.szName;
    }

  private:
    void             setupDefaultWS(const SMonitorRule&);
    int              findAvailableDefaultWS();

    wl_event_source* doneSource = nullptr;

    struct {
        CHyprSignalListener frame;
        CHyprSignalListener destroy;
        CHyprSignalListener state;
        CHyprSignalListener needsFrame;
        CHyprSignalListener presented;
        CHyprSignalListener commit;
    } listeners;
};
